
local require = require;
local ngx     = ngx;
local pairs   = pairs;
local string  = string;
local table   = table
local type    = type
local math_floor = math.floor
local xpcall = xpcall

local table_concat = table.concat
local table_sort = table.sort

local string_sub = string.sub;
local tonumber = tonumber


local ns_env      = require("modules.env.env" )
local uu          = require("modules.commhm.utils")
local log         = require("modules.commhm.log")
local ns_time     = require("modules.commhm.time")
local ns_network  = require("modules.commhm.network")


local ns_http           = require("modules.commhm.resty.http");
local CServersApiClient = require("modules.commhm.framework.CServersApiClient");

local ns_config    = require("modules.dc_gate.dc_gate_config");
local gg           = require("modules.dc_gate.dc_gate_gg");

local ns_auth_checker = require("modules.dc_gate.auth_checker");

local CNgxTimerWrap   = require("modules.commhm.ngxTimerWrap" )

--local ns_luaConfigMgr  =  require("modules.center_server.luaConfigProxy")
local ns_centerServerProxy   = require("modules.commhm.centerServerProxy");


local FOO = {
	_VERSION = '0.12.1',
	acts = {},

	tj   = {},   --统计

	call_count      = 0,
	call_count_rate = 10,
	--call_last_time  = uu.now(),

	worker_id = ngx.worker.id(),

	report_ServerMonitor_name =  "dc_gate" .. (ns_env.report_ServerMonitor_name or '');

	--ns_config.servers --配置文件中的路由地址
	ext_servers = {},   --试图从center_server中获得的额外地址(php和工具使用)

};



--log rate
FOO.call_count_rate = ns_config.call_count_rate or 10;


FOO.enter = function()

	ngx.ctx.m_params = ngx.req.get_uri_args();
	ngx.ctx.call_act_cmd = ngx.ctx.m_params.func or ngx.ctx.m_params.cmd

	---用来检查value的lua格式
	if  ngx.ctx.call_act_cmd == 'check_lua' then
		FOO.check_post_value_lua()
		return
	end

	---dc网关循环穿透
	if  ngx.ctx.m_params.from_dc_gate then
		log.error( "error_find_from_dc_gate" )
		log.day_list( "error", "error_find_from_dc_gate" )
		ngx.say("ERROR_GATE");
		return
	end

	if  gg.check_ip_allow_by_name() > 0 then
		return
	end

	local request_args_ = ngx.var.args .. gg.gen_dc_gate_token()  -- dc_gate到dc的验证key
	ngx.req.set_uri_args( request_args_ );

	--在proxy完成之前，不能打印任何日志
	FOO.private_req_cmd_act_proxy( ngx.ctx.m_params.name )
	--uu.var_dump(ngx.ctx.m_params)

	if  ngx.ctx.m_params.log then
		if  ngx.ctx.m_params.urlinfo == '1' then
			log.day( "urlinfo", "===================dc_gate==================" );
			ngx.req.read_body();
			log.day( "urlinfo", "post= " .. table.tostring(ngx.req.get_post_args()) );
			log.day( "urlinfo", "headers= " .. table.tostring(ngx.req.get_headers()) );

			log.day( "urlinfo", "IP xff= " .. (ngx.ctx.xff or 'nil') .. "-"
					.. ngx.var.remote_addr .. ":" .. ngx.var.remote_port );
			log.day( "urlinfo", "args= " .. ngx.var.args );

			log.day( "urlinfo", "request_uri= " .. ngx.var.request_uri );
			log.day( "urlinfo", "document_uri= " .. ngx.var.document_uri );
			log.day( "urlinfo", "url= " .. ngx.var.uri );
			log.day( "urlinfo", "query_string= " .. ngx.var.query_string );
			log.day( "urlinfo", "new args= " .. (request_args_ or 'nil') );

			--ngx.req.set_uri_args("a=nothing&log=1");
			--log.debug( ngx.var.query_string );
		end
		ns_time.print_cost_all( "gate_" );
	end

end



FOO.tj_caller = function( p_name_, slant_name_, cost_ )

	FOO.call_count = FOO.call_count + 1

	if  not FOO.tj[ slant_name_ ] then
		FOO.tj[ slant_name_ ] = {}
	end

	local name_ = p_name_ .. '_' .. ngx.ctx.call_act_cmd     --call_act_cmd=comm

	if  not FOO.tj[ slant_name_ ][ name_ ] then
		FOO.tj[ slant_name_ ][ name_ ] = { ns7=0, cost=0 }
	end

	FOO.tj[ slant_name_ ][ name_ ].ns7  = FOO.tj[ slant_name_ ][ name_ ].ns7 + 1
	FOO.tj[ slant_name_ ][ name_ ].cost = FOO.tj[ slant_name_ ][ name_ ].cost + cost_
end


FOO.print_tj = function()
	local cost_time_ = 30

	--- 1 写到 share_dict 中
	local worker_id_ = FOO.worker_id

	log.day_list( "dc_gate1", "====set_share_dict", worker_id_, FOO.call_count )
	FOO.tj.call_count = FOO.call_count or 0
	uu.set_server_cache_table( worker_id_, "dc_gate_tj_" .. worker_id_, FOO.tj, 3600 )
	FOO.call_count = 0
	FOO.tj = {}

	local report_monitor = {}  --上报给monitor的数据

	if  worker_id_ == 0 then
		log.day_list( "dc_gate1", "========", worker_id_ )

		local tj_all_ = {}
		local tj_call_count_, tj_call_count2_ = 0, 0   ---用来验算s7和s7n之和

		for i=0, ngx.worker.count() do
			local key_ = "dc_gate_tj_" .. i
			local tj_ = uu.get_server_cache_table( i, key_ )

			if  tj_ and tj_.call_count then
				log.day_list_simple( "dc_gate1", "combine_tj", i, tj_.call_count )
				tj_call_count_ = tj_call_count_ + tj_.call_count
				tj_.call_count = nil

				for k, v in pairs(tj_)  do
					if  not tj_all_[ k ] then
						tj_all_[ k ] = {}
					end

					for m, n in pairs( v ) do
						if  not tj_all_[k][m] then
							tj_all_[k][m] = {ns7=0, cost=0}
						end
						tj_all_[k][m].ns7  = tj_all_[k][m].ns7  + n.ns7
						tj_all_[k][m].cost = tj_all_[k][m].cost + n.cost
					end
				end
			end
		end

		----合并统一打印
		local print_list_ = {}
		for k, v in pairs(tj_all_) do
			for m, n in pairs( v ) do
				local all_ = n.ns7
				tj_call_count2_ = tj_call_count2_ + all_

				if  ns_config.debug == 1 or all_* 1000 > tj_call_count_ then    -- 100*1000>100000 超过千分之一

					print_list_[ #print_list_ + 1 ] = {
						cc  = all_,
						txt = table_concat( {	'a=' .. math_floor(all_/cost_time_),
												't=' .. math_floor(n.cost/all_),
												'a=' .. all_ .. '/' .. cost_time_,
												't=' .. n.cost .. '/' .. all_, k, m }, '|' )
					}
				end

				if  #k < 128 then
					local info_monitor = {
						from = FOO.report_ServerMonitor_name,
						to   = k,         --path:    /miniw/map@120.24.64.132:8079
						code = m,         --act cmd: get_maps
						call = all_,      --call count
						cost = n.cost,    --cost time
					}
					report_monitor[ #report_monitor + 1 ] = info_monitor
				end
			end
		end


		----统计所有block的值
		local all_block_ = 0
		local block_tj_ = tj_all_[ '_block_@127.0.0.1:0' ]
		if  block_tj_ then
			for m, n in pairs( block_tj_ ) do
				all_block_ = all_block_ + n.ns7
			end
		end

		---按call cc值排序打印
		local function sort_func_( a_, b_ )
			return a_.cc < b_.cc
		end
		table_sort( print_list_, sort_func_ )

		for i=1, #print_list_ do
			log.day_list( "dc_gate1", print_list_[i].txt )
		end

		log.day_list( "dc_gate1", "================total=" .. tj_call_count2_,
			"ava=" .. tj_call_count_ .. "/" .. cost_time_ .. "=" .. math_floor(tj_call_count_/cost_time_) )

		---上报到center
		ns_centerServerProxy.WWW_report_load( FOO.report_ServerMonitor_name, "dc_gate", nil,
				tj_call_count_, all_block_, nil, gg.ver_lua, gg.ver_hot )

		---上报到monitor goserver
		if  #report_monitor > 0 then
			local json_str_ = uu.table2json( report_monitor )
			local t_ = { cmd = "report" }
			CServersApiClient.ServerMonitor_report( t_, json_str_ )
		end

	end
end



---定制业务返回值
FOO.block_return = {
}


---- 流量控制
FOO.flow_count = 0
FOO.flow_count_time = 0
FOO.flow_tj = { call=0, block=0, cmd=0 }

--透传协议1
FOO.private_req_cmd_act_proxy = function( p_name_ )
	--log.debug( "call private_req_cmd_act_proxy" )
	local name_    = p_name_ or 'no_name'

	--local slant_name_ = ( FOO.private_trim_last_slant(ngx.var.document_uri) ) or 'nil'
	local t_ = 0       -- cost_time

	local slant_name_ = ngx.var.document_uri or 'nil'
	local path_ = ns_config.servers[ slant_name_ ]

	--- dc dc2 dc3 dc4分流
	if  not path_ then
		log.error( "error_document_uri:" .. slant_name_ )
		return
	end

	--- 按name分流  posting map profile
	if  not path_ [ name_ ] then

		-- 试图从center_server获得配置文件和地址
		local new_path_
		if  ngx.ctx.m_params.from == 'php' then
			new_path_ = FOO.tryGetPathFromExtServer( name_, slant_name_ )    --ma+dc map+dc2
		end

		if  not new_path_ then
			ngx.say( "error: no dc_svr or name" );
			FOO.report_ft_error( 'error_no_dc_svr_name', ngx.var.request_uri )
			log.day_list( "error", "dc_gate_not_in_servers1", slant_name_, name_, ngx.var.request_uri );
			FOO.tj_caller( name_, slant_name_ .. '@no_svr', ns_time.get_all_cost() );
			return
		else
			path_ = new_path_
		end

	else
		path_ = path_ [ name_ ]
	end

	if  path_.select then
		--- 可选按select再次分流dc

		if  ngx.ctx.call_act_cmd == 'mm_read' then
			-- 有select分流配置的时候，dc_gate会代理多路分流  ( 目前 dc_mysql模式独有 )
			FOO.handle_mm_read( name_, path_ )
			return
		end

		--- select值会用到两个地方： 选择dc和选择mysql落地
		--- 比如select=123456, 如果dc分10个，mysql分100个，则选择dc=6 和 mysql=56
		--- 这里dc_gate只会选择dc=6,  mysql=56落地由dc服去选择，与这里无关
		local select_
		if  ngx.ctx.m_params.select then
			select_ = tonumber(ngx.ctx.m_params.select)
		elseif  ngx.ctx.m_params.key then
			select_ = tonumber(ngx.ctx.m_params.key)
			if  not select_ then
				log.day_list( "error", "key_select_not_number", uu.to_str( ngx.ctx.m_params.key ) )
				log.error( "key_select_not_number:" .. uu.to_str( ngx.ctx.m_params.key ) )
			end
		end

		if  select_  then
			select_ = select_ % (path_.select.select_mod or 10)  ---默认10分表，一般也最多分10台机器
			if  path_.select[select_] then
				path_ = path_.select[select_]
			end
		end
	end


	if  type(path_) == 'table' and path_.ip and path_.port then
		---配置正常
	else
		FOO.report_ft_error( 'error_config_ip_port', ngx.var.request_uri )
		log.day_list( "error", "error_dc_gate_config", uu.to_str(slant_name_) )
		ngx.say( "error_config:" .. uu.to_str(slant_name_) );
		return
	end

	t_ = ns_network.proxyHttp(  path_.ip, path_.port );   ---透传cmd
	FOO.flow_tj.cmd  = FOO.flow_tj.cmd + 1
	FOO.flow_count   = FOO.flow_count + 1
	FOO.tj_caller( name_, slant_name_ .. '@' .. path_.ip .. ':' .. path_.port, ns_time.get_all_cost() );

	---control flow
	if  uu.now() == FOO.flow_count_time then
		FOO.flow_count = FOO.flow_count + 1
	else
		---another second
		FOO.flow_count = 0
		FOO.flow_count_time = uu.now()
		local log_content_ = uu.to_str( FOO.flow_tj )
		FOO.flow_tj = { call=0, block=0, cmd=0 }
		log.day_list( "system", "flow", FOO.worker_id, log_content_ )
	end


	if  FOO.call_count == 1 then
		---- 摘样日志每个worker的第1笔，用来查看reuse值
		if  path_ then
			log.day_list( "access_dc_gate", "ok", FOO.worker_id, t_, ns_time.get_all_cost(),
					gg.get_url_by_path(path_) .. ngx.var.request_uri );
		end
	end

end



--ngx.var.document_uri
FOO.private_trim_last_slant = function( name_ )
	if  name_ and #name_ > 0 then
		if  string_sub( name_, -1 ) == "/" then
			return string_sub( name_, 1, -2 );
		end
	end
	return name_;
end


--集合批量读取
function FOO.handle_mm_read( name_, path_ )
	--name=acc_new&cmd=mm_read&kv_list=123456-3-1166201-2-1234516-3-1234526-3&select_list=123-123-123-123
	local kv_list_info_ = {}

	-- 获得必要参数 kv_list
	local kv_list_,  select_list_
	if  ngx.ctx.m_params.kv_list then
		kv_list_     = ngx.ctx.m_params.kv_list
		select_list_ = ngx.ctx.m_params.select_list
	else
		--- 在post里查找
		ngx.req.read_body();
		local args_ = ngx.req.get_post_args();
		kv_list_     = args_.kv_list
		select_list_ = args_.select_list
	end

	if  not ( type(kv_list_) == 'string' ) then
		log.error( "not_find_kv_list" )  --必要参数错误
		return
	end


	-- 1 拆分 kv_list
	local t_ = uu.split( kv_list_, '-' )
	for i=1, #t_, 2 do
		if  t_[i] and t_[i+1] then
			kv_list_info_[ #kv_list_info_ + 1 ] = { key=t_[i], hash=t_[i+1] }
		end
	end

	if  #kv_list_info_ <= 0 then
		log.error( "error_kv_list_size" )  --必要参数错误
		return
	end


	-- 2 拆分 select_list
	if  select_list_ then
		local ts_ = uu.split( select_list_, '-' )
		if  not ( #ts_ == #kv_list_info_ ) then
			log.error( "error_num_select_list" )  --必要参数个数错误
			return
		end

		for i=1, #ts_ do
			kv_list_info_[ i ].select = tonumber(ts_[i])   --指定了select
		end
	else
		-- 没有指定，则select = key
	end


	--取分表mod值
	local select_mod_ = 10
	if  path_.select and path_.select.select_mod then
		select_mod_ = path_.select.select_mod
	end


	--通过select  计算tail和url
	for i=1, #kv_list_info_ do
		local key_num_
		if  kv_list_info_[i].select then
			key_num_ = kv_list_info_[i].select
		else
			key_num_ = tonumber( kv_list_info_[i].key )
		end
		if  key_num_ then
			kv_list_info_[i].tail = key_num_ % select_mod_
		end
	end

	uu.var_dump( kv_list_info_ )

	-- 3 计算url
	local fret_ = gg.multi_mm_read_data_thread( name_, kv_list_info_, path_ )
	gg.return_table( fret_ )

end


---检查value的lua语法
function FOO.check_post_value_lua()
	ngx.req.read_body()
	local post_ = ngx.req.get_post_args()
	if  post_.value then
		--进行lua解析
		local ret = uu.safe_loadstring( post_.value );
		if  uu.tableIsEmpty( ret ) then
			ngx.say("error_empty_table");
			return
		else
			ngx.say("OK_LUA");
			return
		end
	else
		ngx.say("error_not_set_value");
		return
	end
	ngx.say("error_table_system");
end


-- 上报严重错误
function FOO.report_ft_error( p_key_, value_ )
	local key_   = 'dc_gate_' .. p_key_
	ns_centerServerProxy.WWW_fatalReport( 1, key_, 10, value_, 1 )
end


--  清除前后斜杠   /dc/ --> dc  /dc2/ --> dc2
function FOO.trim_slant_name(slant_name_)
	return string.sub( slant_name_, 2, (#slant_name_-1) )
end


-- 试图从center_server中获得新地址   --ma+dc map+dc2
function FOO.tryGetPathFromExtServer( name_, slant_name_ )
	--FOO.ext_servers[ slant_name_ ][ name_ ] = ext_config_server1_
	if  FOO.ext_servers[ slant_name_ ] and FOO.ext_servers[ slant_name_ ][ name_ ] then
		log.day_list( "dc_gate", "get_ext_servers", name_, slant_name_, uu.to_str(FOO.ext_servers) )
		return FOO.ext_servers[ slant_name_ ][ name_ ]
	end

	return FOO.private_set_dc_config( name_, slant_name_ )
end


-- 发起远程调用，试图从center_server中获得新地址   --ma+dc map+dc2
function FOO.private_set_dc_config( name_, slant_name_ )
	local  slant_name_short_ = FOO.trim_slant_name(slant_name_)

	local key_ = name_ .. '_' .. slant_name_short_    --map_dc2
	if  not uu.check_worker_time_interval( 'gdc_' .. key_, 2 ) then
		log.debug( 'ignore:check_server_time_interval:gdc_' .. key_ )
		return
	end

	local config_ret_ = ns_centerServerProxy.WWW_getConfigReport( key_ )
	log.day_list( "dc_gate", "WWW_getConfigReport", name_, slant_name_short_, uu.to_str(config_ret_) )

	--{ server={} select={} }
	local  ext_config_server1_ = gg.decode_dc_client_config( config_ret_ )
	if  type(ext_config_server1_) == 'table' then
		if  not FOO.ext_servers[ slant_name_ ] then
			FOO.ext_servers[ slant_name_ ] = {}
		end
		FOO.ext_servers[ slant_name_ ][ name_ ] = ext_config_server1_
		log.day_list( "dc_gate", "set_ext_servers", name_, slant_name_short_, uu.to_str(FOO.ext_servers) )

		--写成文件记录到本地 待查
		local file_name_ = '/data/centerConfigServer/dc_client_config/gate_' .. ns_config.debug .. '_' .. key_
		uu.create_dir_if( '/data/centerConfigServer/dc_client_config/' )
		uu.write_file_content( file_name_, table.tostring(ext_config_server1_) )

		return ext_config_server1_
	end

	log.error( "WWW_getConfigReport:" .. key_ .. ', body=' .. uu.to_str(config_ret_) )
end



-- 启动初始化
FOO.init = function()
	---- 设置本集群名字:  nil=普通数据网关  不为空=下沉节点网关( 比如Asia1, Europe1)
	FOO.server_cluster_name = ngx.shared.miniw:get( 'server_cluster_name' )
	log.day_list( 'dc_gate', 'init', ngx.worker.id(), 'dc_gate', FOO.report_ServerMonitor_name,
			'server_cluster_name=', uu.to_str(FOO.server_cluster_name) )


	--- 尝试加载 本集群 下沉路由
	if  FOO.server_cluster_name then

		-- lua/modules/dc_gate/config/dc_gate_config1_Asia1.lua
		local file_name_ = 'modules.dc_gate.config.dc_gate_config' .. ns_env.debug .. '_' .. FOO.server_cluster_name

		local function load_cluster_config_()
			local tmp_config_ = require( file_name_ )
			if  type(tmp_config_) == 'table' and tmp_config_.servers then
				ns_config.servers = tmp_config_.servers    --替换掉原有的配置
				log.day_list( 'dc_gate', 'init', ngx.worker.id(), 'reset_servers', FOO.server_cluster_name,
						uu.get_var_dump_string_text( ns_config.servers ) )
			else
				log.day_list( 'dc_gate', 'init', ngx.worker.id(), 'reset_servers', 'load_config_error' )
			end
		end

		local function show_error_( errmsg_ )
			log.day_list( 'dc_gate', 'init', ngx.worker.id(), 'xpcall_show_error_', errmsg_ )
		end

		xpcall( load_cluster_config_, show_error_ )
	end

	gg.init_ip_allow()


	local timer_ = CNgxTimerWrap:new()
	return timer_:ngx_timer_every( 30, FOO.print_tj )
end


FOO.init();
return FOO;
